<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 */

/**
 * XHTML writer
 *
 * PHP version 5
 *
 * LICENSE: This file is part of myLib.
 *
 * myLib is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * myLib is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @category Writer
 * @package myLib
 * @author Diego Machado de Assis <manejoao@gmail.com>
 * @copyright 2009 Diego Machado de Assis
 * @license http://www.gnu.org/licenses/gpl.html GNU GENERAL PUBLIC LICENSE Version 3
 * @see xmloutputter.php
 */

require_once 'xmloutputter.php';

/**
 * XHTML writer
 *
 * This class implements various methods that abstract the written of XHTML
 * elements. Note that not all XHTML language is covered by this class, it means
 * that there is not a method for each element of the language. To write
 * elements that don't have an especific creation method, you should use the
 * methods to create generic XML elements, implemented at XMLOutputter class.
 *
 * @category Writer
 * @package myLib
 * @author Diego Machado de Assis <manejoao@gmail.com>
 * @copyright 2009 Diego Machado de Assis
 * @license http://www.gnu.org/licenses/gpl.html GNU GENERAL PUBLIC LICENSE Version 3
 * @see XMLOutputter
 */
class XHTMLOutputter extends XMLOutputter {

    /**
     * Constructor
     *
     * Just call the XMLOutputter constructor
     *
     * @param string    $output output file to the writer. Defaults to stdout
     * @param boolean   $indent wheter the output should be indented. Defaults
     *                          to TRUE
     *
     * @access public
     * @see XMLOutputter::__construct
     */
    public function __construct($output='php://output', $indent=true) {
        parent::__construct($output, $indent);
    }

    /**
     * Start the XHTML document
     *
     * Creates the DTD declaration (Doctype) and starts the html tag of the
     * document.
     *
     * If $mimeType is not specified, it will atempt to do the mime
     * type negotiation. Type 'application/xhtml+xml' is allways prefered.
     *
     * @param string    $lang           the language of the document,
     *                                  accordingly to RFC 3066. Defaults to
     *                                  'pt-br'
     * @param string    $encoding       encoding of the document. Defaults to
     *                                  'UTF-8'
     * @param string    $xmlVersion     the version of XML document. Defaults to
     *                                  '1.0'
     * @param string    $xhtmlVersion   the version of XHTML document. It can be
     *                                  valued to:
     *                                      - 'xhtml11': represents a XHTML 1.1
     *                                      document;
     *                                      - 'xhtml10-strict': represents a
     *                                      XHTML 1.0 scritc conformed document;
     *                                      - 'xhtml10-transitional': represents
     *                                      a XHTML 1.0 transitional document.
     *                                  Defaults to 'xhtml11'
     * @param string    $mimeType       the mime type that the document will be
     *                                  served as. If it is not specified, it
     *                                  will be setted by the method
     *                                  XHTML::negotiateType()
     *
     * @return boolean  TRUE if the document could be succefully started, FALSE
     *                  otherwise
     *
     * @access public
     * @see XHTMLOutputter::XHTMLEnd()
     */
    public function XHTMLStart($lang='pt-br', $encoding='UTF-8',
            $xmlVersion='1.0', $xhtmlVersion='xhtml11', $mimeType=null) {

        switch($xhtmlVersion){

            // XHTML 1.1
            case 'xhtml11':
                $dtd_name = 'html';
                $dtd_public_identifier = '-//W3C//DTD XHTML 1.1//EN';
                $dtd_system_identifier =
                    'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd';
                $namespace = "http://www.w3.org/1999/xhtml";
                $xsi = 'http://www.w3.org/2001/XMLSchema-instance';
                $schema_location =
                    'http://www.w3.org/MarkUp/SCHEMA/xhtml11.xsd';
                $html_attrs = array('xmlns' => $namespace,
                        'xmlns:xsi' => $xsi,
                        'xsi:schemaLocation' => $schema_location,
                        'xml:lang' => $lang);
                $server_accept =
                    'text/html;q=0.1,application/xhtml+xml,application/xml;q=0.3,text/xml;q=0.2';
                break;

            // XHTML 1.0 Strict
            case 'xhtml10-strict':
                $dtd_name = 'html';
                $dtd_public_identifier = '-//W3C//DTD XHTML 1.0 Strict//EN';
                $dtd_system_identifier =
                    'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd';
                $namespace = "http://www.w3.org/1999/xhtml";
                $html_attrs = array('xmlns' => $namespace,
                        'xml:lang' => $lang,
                        'lang' => $lang);
                $server_accept =
                    'text/html;q=0.5,application/xhtml+xml,application/xml;q=0.3,text/xml;q=0.2';
                break;

            // XHTML 1.0 Transitional
            default:
                $dtd_name = 'html';
                $dtd_public_identifier = '-//W3C//DTD XHTML 1.0 Transitional//EN';
                $dtd_system_identifier =
                    'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd';
                $namespace = "http://www.w3.org/1999/xhtml";
                $html_attrs = array('xmlns' => $namespace,
                        'xml:lang' => $lang,
                        'lang' => $lang);
                $server_accept =
                    'text/html;q=0.5,application/xhtml+xml,application/xml;q=0.3,text/xml;q=0.2';
                break;

        }

        if(!$mimeType) {
            $client_accept = isset($_SERVER['HTTP_ACCEPT']) ?
                $_SERVER['HTTP_ACCEPT'] : null;

            $client_prefs = $this->acceptToPrefs($client_accept);
            $server_prefs = $this->acceptToPrefs($server_accept);

            $type = $this->negotiateType($client_prefs, $server_prefs);
            if(!$type){
                /* XXX: Print error */
                return false;
            }
        }

        header('content-type: ' . $type);

        if( $this->XMLStart($encoding, $xmlVersion) &&
                $this->dtd($dtd_name, $dtd_public_identifier,
                    $dtd_system_identifier) &&
                $this->elementStart('html', $html_attrs) ) {
            return true;
        }
        return false;
    }

    /**
     * End the XHTML document
     *
     * End the html tag and calls the XMLOutputter::XMLEnd() method.
     *
     * @return boolean  TRUE on success or FALSE on failure
     *
     * @access public
     * @see XHTMLOutputter::XHTMLStart()
     */
    public function XHTMLEnd(){
        return $this->elementEnd('html') && $this->XMLEnd();
    }

    /* Following functions are copied from MediaWiki GlobalFunctions.php
     * and written by Evan Prodromou. */

    private function acceptToPrefs($accept, $def = '*/*'){

        // No arg means accept anything (per HTTP spec)
        if(!$accept){
            return array($def => 1);
        }

        $prefs = array();

        $parts = explode(',', $accept);

        foreach($parts as $part){
            // FIXME: doesn't deal with params like 'text/html; level=1'
            @list($value, $qpart) = explode(';', $part);
            $match = array();
            if(!isset($qpart)){
                $prefs[$value] = 1;
            }
            elseif(preg_match('/q\s*=\s*(\d*\.\d+)/', $qpart, $match)){
                $prefs[$value] = $match[1];
            }
        }

        return $prefs;
    }

    private function mimeTypeMatch($type, $avail){
    
        if(array_key_exists($type, $avail)){
            return $type;
        }
        else{
            $parts = explode('/', $type);
            if(array_key_exists($parts[0] . '/*', $avail)){
                return $parts[0] . '/*';
            }
            elseif(array_key_exists('*/*', $avail)){
                return '*/*';
            }
            else{
                return null;
            }
        }
    }
    
    private function negotiateType($cprefs, $sprefs){

        $combine = array();

        foreach(array_keys($sprefs) as $type){
            $parts = explode('/', $type);
            if($parts[1] != '*'){
                $ckey = $this->mimeTypeMatch($type, $cprefs);
                if($ckey) {
                    $combine[$type] = $sprefs[$type] * $cprefs[$ckey];
                }
            }
        }

        foreach(array_keys($cprefs) as $type){
            $parts = explode('/', $type);
            if($parts[1] != '*' && !array_key_exists($type, $sprefs)){
                $skey = $this->mimeTypeMatch($type, $sprefs);
                if($skey){
                    $combine[$type] = $sprefs[$skey] * $cprefs[$type];
                }
            }
        }

        $bestq = 0;
        $besttype = "text/html";

        foreach(array_keys($combine) as $type){
            if($combine[$type] > $bestq){
                $besttype = $type;
                $bestq = $combine[$type];
            }
        }

        return $besttype;
    }

}
